#include "StdAfx.h"
#include "WMPPlus.h"
#include "..\WMP\Common.h"
#include "WMPPlusHook.h"
#include "WMPUtils.h"
#include "WMPPlusHookData.h"
#include "..\WMP\RWLocker.h"
#include <Shlwapi.h>

#pragma unmanaged

#define WINDOW_PROC_PROP _T("{C064A975-1459-4B57-9BA2-42326B9148A3}") //_T("{9904DEBE-CA17-434B-B464-7769A79DD3B0}")

WMPPlus* Plugin = NULL;
CriticalSectionLocker PluginLocker;

LRESULT CALLBACK WMPPlayerWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT WINAPI WMPLibraryWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void HookWindowProc(HWND hWnd, WNDPROC proc);
LRESULT CALLBACK GetMessageHookProc(int code, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK CallWndHookProc(int code, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK CallWndHookRetProc(int code, WPARAM wParam, LPARAM lParam);
void HookLibraryWindowProc(bool arg);

LRESULT CALLBACK WMPPlayerWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
	bool origin = true;
	LRESULT res = 0;
	try {
		if (Plugin) {
			//TODO: if (Core->MediaHook->f4 == uMsg) { restore position }
			switch(uMsg) {
			case WM_SETTEXT:
				if (lParam && Plugin->Addons)
					origin = Plugin->Addons->ProcessSetTextMessage((LPCTSTR)lParam, res);
				break;
			case WM_NCPAINT:
			case WM_NCUAHDRAWCAPTION:
			case WM_NCUAHDRAWFRAME:
				origin = !Plugin->getInTitleDraw();
				break;
			}
		}
	} finally (
		if (origin) {
			WNDPROC proc = (WNDPROC)GetProp(hwnd, WINDOW_PROC_PROP);
			if (proc)
				res = CallWindowProc(proc, hwnd, uMsg, wParam, lParam);
			else
				res = DefWindowProc(hwnd, uMsg, wParam, lParam);
		}
	)
	if (uMsg == WM_SETTINGCHANGE) {
		OutputDebugString(_T("TODO: update long time format\r\n"));
		//TODO: update long time format
	}
	return res;
}

LRESULT WINAPI WMPLibraryWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
	bool origin = true;
	LRESULT res = 0;
	try {
		if (Plugin) {
			if (Plugin->getLibraryMenu() && false) {
				OutputDebugString(_T("TODO: if (Plugin.WMPFileContext != null && Plugin.LibraryMenu != null) origin = Plugin.WMPFileContext.sub_436D04(hwnd, uMsg, &res, lParam, wParam); else\r\n"));
				//TODO: if (Plugin.WMPFileContext != null && Plugin.LibraryMenu != null) origin = Plugin.WMPFileContext.sub_436D04(hwnd, uMsg, &res, lParam, wParam); else
			} else if (uMsg == LVM_ENABLEGROUPVIEW) {
				if (wParam && Plugin->Addons)
					Plugin->Addons->FixListViewGroupViewMessage(wParam);
			}
		}
	} finally (
		if (origin) {
			WNDPROC proc = (WNDPROC)GetProp(hwnd, WINDOW_PROC_PROP);
			if (proc)
				res = CallWindowProc(proc, hwnd, uMsg, wParam, lParam);
			else
				res = DefWindowProc(hwnd, uMsg, wParam, lParam);
		}
	)
	return res;
}

void HookWindowProc(HWND hWnd, WNDPROC proc) {
	if (proc) {
		if (!GetProp(hWnd, WINDOW_PROC_PROP)) {
			SetProp(hWnd, WINDOW_PROC_PROP, (HANDLE)GetWindowLongPtr(hWnd, GWLP_WNDPROC));
			SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)proc);
		}
	} else {
		proc = (WNDPROC)GetProp(hWnd, WINDOW_PROC_PROP);
		if (proc)
			SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)proc);
		RemoveProp(hWnd, WINDOW_PROC_PROP);
	}
}

LRESULT CALLBACK GetMessageHookProc(int code, WPARAM wParam, LPARAM lParam) {
	LRESULT res = CallNextHookEx(!Plugin ? NULL : Plugin->getGetMessageHook(), code, wParam, lParam);
	if (code < 0 || wParam != PM_REMOVE || !Plugin) return res;
	MSG* msg = (MSG*)lParam;
	WMPUIAddons* addons; bool invoked = false;
	switch(msg->message) {
	case WM_CHAR:
		addons = Plugin->Addons;
		if (addons && addons->ProcessKey(msg->hwnd, (WORD)msg->wParam, (DWORD)msg->lParam))
			msg->message = 0u;
		break;
	case WM_COMMAND:
		if ((msg->wParam >> 0x10 != 0) && (msg->wParam >> 0x10 != 1) || msg->lParam)
			break;
		if (Plugin->Menu)
			invoked = Plugin->Menu->InvokeMenuItem(msg->hwnd, (WORD)msg->wParam);
		OutputDebugString(_T("TODO: if (!invoked && Plugin.WMPFileContext) Plugin.WMPFileContext.sub_436C94(msg->hwnd, (WORD)msg->wParam);\r\n"));
		//TODO: if (!invoked && Plugin.WMPFileContext)
		//TODO: 	Plugin.WMPFileContext.sub_436C94(msg->hwnd, (WORD)msg->wParam);
		break;
	case WM_KEYDOWN:
		OutputDebugString(_T("TODO: if (Core->WMPHotkeys->sub_53D270(msg->hwnd, (WORD)msg->wParam, msg->lParam)) msg->message = 0u; else Plugin->WMPPartyShuffle->sub_4C52F0(msg->hwnd, (WORD)msg->wParam, msg->lParam);\r\n"));
		//TODO: if (Core->WMPHotkeys->sub_53D270(msg->hwnd, (WORD)msg->wParam, msg->lParam)) msg->message = 0u; else Plugin->WMPPartyShuffle->sub_4C52F0(msg->hwnd, (WORD)msg->wParam, msg->lParam);
	default:
		if (msg->message && msg->message == Plugin->getPluginAddRemoveMsg() && Plugin->getWMPlayerWindow() == msg->hwnd) {
			OutputDebugString(_T("TODO: WMPPlusCore.PluginManager.Init();\r\n"));
			//TODO: WMPPlusCore.PluginManager.Init();
		}
		break;
	}
	return res;
}

LRESULT CALLBACK CallWndHookProc(int code, WPARAM wParam, LPARAM lParam) {
	LRESULT res = CallNextHookEx(!Plugin ? NULL : Plugin->getCallWndProcHook(), code, wParam, lParam);
	if (code < 0 || !Plugin) return res;
	CWPSTRUCT* msg = (CWPSTRUCT*)lParam;
	switch (msg->message) {
	case WM_CREATE:
		Plugin->HookLibraryWindowProc(msg->hwnd, (CREATESTRUCT*)msg->lParam);
		break;
	case WM_INITMENUPOPUP:
		if (!(msg->lParam >> 0x10))
			Plugin->InitPopupMenu(msg->hwnd, (HMENU)msg->wParam);
		break;
	case WM_UNINITMENUPOPUP:
		Plugin->UninitPopupMenu(msg->hwnd, (HMENU)msg->wParam);
		break;
	}
	return res;
}

LRESULT CALLBACK CallWndHookRetProc(int code, WPARAM wParam, LPARAM lParam) {
	LRESULT res = CallNextHookEx(!Plugin ? NULL : Plugin->getCallWndProcRetHook(), code, wParam, lParam);
	if (code < 0 || !Plugin) return res;
	CWPRETSTRUCT* msg = (CWPRETSTRUCT*)lParam;
	switch(msg->message) {
	case WM_INITMENUPOPUP:
		if (!(msg->lParam >> 0x10) && Plugin->Menu)
			Plugin->Menu->InvokeSubMenu(msg->hwnd, (HMENU)msg->wParam);
		break;
	}
	return res;
}

WMPPlus::WMPPlus() : WMPPlusPublic() {
	SHGetDesktopFolder(&this->shell);
}

WMPPlus::~WMPPlus() {
	if (this->shell)
		this->shell->Release();
	this->shell = NULL;
}

void WMPPlus::Initialize() {
	WMPPlusPublic::Initialize();
	setLibraryMenu(NULL);
	setPluginAddRemoveMsg((int)RegisterWindowMessage(_T("WMPlayer_PluginAddRemove")));
	HWND hWnd = getWMPlayerWindow();
	if (hWnd) {
		DWORD thread = GetWindowThreadProcessId(hWnd, NULL);
		HHOOK hook;
		hook = SetWindowsHookEx(WH_GETMESSAGE, GetMessageHookProc, NULL, thread);
		setGetMessageHook(hook);
		hook = SetWindowsHookEx(WH_CALLWNDPROC, CallWndHookProc, NULL, thread);
		setCallWndProcHook(hook);
		hook = SetWindowsHookEx(WH_CALLWNDPROCRET, CallWndHookRetProc, NULL, thread);
		setCallWndProcRetHook(hook);
		HookWindowProc(hWnd, WMPPlayerWindowProc);
		HookLibraryWindowProc(true);
	}
	Menu = new WMPMenu();
	Addons = new WMPUIAddons(this);
	HookWMP();
}

void WMPPlus::Dispose() {
	HookLibraryWindowProc(false);
	HookWindowProc(getWMPlayerWindow(), NULL);
	HHOOK hook;
	hook = getGetMessageHook();
	if (hook)
		UnhookWindowsHookEx(hook);
	setGetMessageHook(NULL);
	hook = getCallWndProcHook();
	if (hook)
		UnhookWindowsHookEx(hook);
	setCallWndProcHook(NULL);
	hook = getCallWndProcRetHook();
	if (hook)
		UnhookWindowsHookEx(hook);
	setCallWndProcRetHook(NULL);
	delete Menu;
	Menu = NULL;
	delete Addons;
	Addons = NULL;
}

void WINAPI WMPPlusInitialize(WMPMenu* &menu) {
	PluginLocker.AcquireLockExclusive();
	try {
		Plugin = new WMPPlus();
	} finally (
		PluginLocker.ReleaseLockExclusive();
	)
	Plugin->Initialize();
	menu = Plugin->Menu;
}

void WINAPI WMPPlusDispose() {
	if (!Plugin) return;
	Plugin->Dispose();
	PluginLocker.AcquireLockExclusive();
	try {
		delete Plugin;
		Plugin = NULL;
	} finally (
		PluginLocker.ReleaseLockExclusive();
	)
}

void WMPPlus::HookLibraryWindowProc(bool arg) {
	HWND hWnd;
	WNDPROC proc;
	if (!arg) proc = NULL;
	else proc = WMPLibraryWindowProc;
	hWnd = FindWMPLibraryListWindow(getWMPlayerWindow());
	if (hWnd)
		HookWindowProc(hWnd, proc);
	hWnd = FindWMPLibraryPlaylistWindow(getWMPlayerWindow());
	if (hWnd)
		HookWindowProc(hWnd, proc);
}

void WMPPlus::HookLibraryWindowProc(HWND hWnd, CREATESTRUCT* info) {
	CreateTextBuffer(classBuffer);

	GetClassName(hWnd, classBuffer, classBufferLength);
	if (lstrcmpi(classBuffer, _T("SysListView32")) == 0 &&
		(lstrcmpi(info->lpszName, _T("PrimaryListView")) == 0 || lstrcmpi(info->lpszName, _T("BasketListView")) == 0)) {
		HookWindowProc(hWnd, WMPLibraryWindowProc);
	} else if (lstrcmpi(classBuffer, _T("WMP Skin Host")) == 0) {
		LONG_PTR style = GetWindowLongPtr(hWnd, GWL_STYLE);
		LONG_PTR exstyle = GetWindowLongPtr(hWnd, GWL_EXSTYLE);
		if (!(style & WS_CHILD) && (exstyle & WS_EX_APPWINDOW)) {
			setWMPLibraryWindow(hWnd);
			if (Addons)
				Addons->SetWindowTitle(hWnd);
		}
	}
}

void WMPPlus::InitPopupMenu(HWND hWnd, HMENU hMenu) {
	CreateTextBuffer(textBuffer);
	CreateTextBuffer(classBuffer);

	SendMessage(hWnd, WM_GETTEXT, textBufferLength, (LPARAM)textBuffer);
	if (GetClassName(hWnd, classBuffer, classBufferLength) <= 0)
		*classBuffer = 0;
	if (lstrcmpi(_T("SysListView32"), classBuffer) != 0) return;
	bool isPlaylist = false;
	if (lstrcmpi(_T("PrimaryListView"), textBuffer) != 0) {
		if (lstrcmpi(_T("BasketListView"), textBuffer) == 0) isPlaylist = true;
		else if (lstrcmpi(_T("Now Playing list"), textBuffer) == 0) isPlaylist = true;
		else return;
	}
	bool isLibrary = false;
	MENUITEMINFO info;
	memset(&info, 0, sizeof(MENUITEMINFO));
	info.cbSize = sizeof(MENUITEMINFO);
	if (GetMenuItemInfo(hMenu, 7243, false, &info) && 
		(GetMenuItemInfo(hMenu, 7240, false, &info) ||
		 GetMenuItemInfo(hMenu, 7261, false, &info))) {
		setLibraryMenu(hMenu);
		setMediaFolder(NULL);
		setSelected(0);
		int selected = 0;
		LPCTSTR medias = NULL;
		if (ResolveCurrentFolder(hWnd, true, selected, medias)) {
			setSelected(selected);
			OutputDebugString(_T("TODO: if (TagEditor) TagEditor.sub_507078(hWnd, hMenu, isPlaylist);\r\n"));
			//TODO: if (TagEditor)
			//TODO: 	TagEditor.sub_507078(hWnd, hMenu, isPlaylist);
			OutputDebugString(_T("TODO: if (Addons) Addons.sub_4CCD2C(hWnd, hMenu, isPlaylist);\r\n"));
			//TODO: if (Addons)
			//TODO: 	Addons->sub_4CCD2C(hWnd, hMenu, isPlaylist);
		}
	}
	OutputDebugString(_T("TODO: if (FileContext) FileContext.sub_436A9C(hWnd, hMenu, isPlaylist, b);\r\n"));
	//TODO: if (FileContext)
	//TODO: 	FileContext.sub_436A9C(hWnd, hMenu, isPlaylist, b);
}

void WMPPlus::UninitPopupMenu(HWND hWnd, HMENU hMenu) {
	if (hMenu == getLibraryMenu()) {
		setLibraryMenu(NULL);
		OutputDebugString(_T("TODO: if (FileContext) FileContext.sub_436C78();\r\n"));
		//TODO: if (FileContext)
		//TODO: 	FileContext.sub_436C78();
	}
}

void WMPPlus::HookWMP() {
	CreatePathBuffer(pathBuffer);

	GetSystemDirectory(pathBuffer, pathBufferLength);
	size_t len = _tcslen(pathBuffer);
	if (len >= 2 && pathBuffer[len-1] == '\\')
		pathBuffer[len-1] = 0;
	_tcscat(pathBuffer, _T("\\wmp.dll"));
	HMODULE hlib = GetModuleHandle(pathBuffer);
	if (!hlib) return;

	PIMAGE_DOS_HEADER dosheader = (PIMAGE_DOS_HEADER)hlib;
	PIMAGE_NT_HEADERS header = (PIMAGE_NT_HEADERS)((char*)hlib + dosheader->e_lfanew);
	PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(header);
	int count = header->FileHeader.NumberOfSections;
	while(count-- > 0 && (((int*)section->Name)[0] != 0x7865742E || ((int*)section->Name)[1] != 0x74)/*.text*/) {
		section++;
	}
	if (count < 0) return;
	char* pStart = (char*)hlib + section->VirtualAddress;
	char* pEnd = pStart + section->SizeOfRawData - 4;

	char* pMagic = NULL;
	char *pMagic1, *pMagic2;
	pMagic1 = pMagic2 = pStart + (section->SizeOfRawData * 427 / 1000);
	while(pMagic1 > pStart && pMagic2 < pEnd) {
		if (*(int*)pMagic1 == 0x616C6163/*alac*/) {
			pMagic = pMagic1;
			break;
		}
		if (*(int*)pMagic2 == 0x616C6163/*alac*/) {
			pMagic = pMagic2;
			break;
		}
		pMagic1--; pMagic2++;
	}
	if (!pMagic) return;

	DWORD flags;
	VirtualProtect(pMagic, 4, PAGE_EXECUTE_READWRITE, &flags);
	try {
		*(int*)pMagic = 0x6C636168;
	}
	finally (
		VirtualProtect(pMagic, 4, flags, &flags);
	)
}

bool ResolveMediaFolder(PCIDLIST_ABSOLUTE pidlFolder, UINT cidl, PCUITEMID_CHILD_ARRAY apidl) {
	bool res = true;
	CreateTextBuffer(textBuffer);

	PluginLocker.AcquireLockShared();
	try {
		if (Host && Plugin && Plugin->getHookFolder()) {
			res = false;
			if ((!Plugin->getResolveFolder() || !Plugin->getMediaFolderList()) && cidl) {
				IShellFolder* folder = NULL;
				HRESULT hr;
				if (!pidlFolder->mkid.cb) {
					hr = S_OK;
					folder = Plugin->getDesktopShellFolder();
					folder->AddRef();
				} else {
					hr = Plugin->getDesktopShellFolder()->BindToObject(pidlFolder, NULL, IID_IShellFolder, (void**)&folder);
				}
				if (SUCCEEDED(hr)) {
					try {
						*textBuffer = 0;
						if (Plugin->getMediaFolderList())
							_tcsncpy(textBuffer, Plugin->getMediaFolderList(), textBufferLength);
						for(; cidl > 0; apidl++,cidl--) {
							if (!*apidl) continue;
							STRRET name;
							folder->GetDisplayNameOf(*apidl, SHGDN_FORPARSING, &name);
							LPTSTR path;
							StrRetToStr(&name, *apidl, &path);
							try {
								if (!Plugin->getMediaFolder())
									Plugin->setMediaFolder(path);
								//Plugin->LoadMedia(path);
								size_t len = _tcslen(textBuffer);
								if (*textBuffer)
									_tcsncat(textBuffer, _T("|"), textBufferLength - len++);
								_tcsncat(textBuffer, path, textBufferLength - len);
							} finally (
								CoTaskMemFree(path);
							)
							if (Plugin->getResolveFolder() && *textBuffer)
								break;
						}
						Plugin->setMediaFolderList(textBuffer);
					} finally (
						if (folder)
							folder->Release();
					)
				}
			}
		}
	} finally (
		PluginLocker.ReleaseLockShared();
	)
	return res;
}
